#include <gtest/gtest.h>
#include "3rd/lua/lua.hpp"

using namespace std;

lua_State* globalL = NULL;
static void lstop(lua_State* L, lua_Debug* ar)
{
    (void)ar;                   /* unused arg. */
    lua_sethook(L, NULL, 0, 0); /* reset hook */
    luaL_error(L, "interrupted!");
}
static void laction(int i)
{
    signal(i, SIG_DFL); /* if another SIGINT happens, terminate process */
    lua_sethook(globalL, lstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1);
}
static int msghandler(lua_State* L)
{
    const char* msg = lua_tostring(L, 1);
    if (msg == NULL)
    {                                            /* is error object not a string? */
        if (luaL_callmeta(L, 1, "__tostring") && /* does it have a metamethod */
            lua_type(L, -1) == LUA_TSTRING)      /* that produces a string? */
            return 1;                            /* that is the message */
        else
            msg = lua_pushfstring(L, "(error object is a %s value)", luaL_typename(L, 1));
    }
    luaL_traceback(L, L, msg, 1); /* append a standard traceback */
    return 1;                     /* return the traceback */
}

class JksBasicGrammar : public testing::Test
{
public:
    virtual void SetUp()
    {
        l = luaL_newstate();
        luaL_openlibs(l);
    }
    virtual void Teardown() { lua_close(l); }

    int dostring(const char* s, const char* name) { return dochunk(l, luaL_loadbuffer(l, s, strlen(s), name)); }

private:
    int dochunk(lua_State* L, int status)
    {
        if (status == LUA_OK)
            status = docall(L, 0, 0);
        return report(L, status);
    }

    int docall(lua_State* L, int narg, int nres)
    {
        int status;
        int base = lua_gettop(L) - narg;  /* function index */
        lua_pushcfunction(L, msghandler); /* push message handler */
        lua_insert(L, base);              /* put it under function and args */
        globalL = L;                      /* to be available to 'laction' */
        signal(SIGINT, laction);          /* set C-signal handler */
        status = lua_pcall(L, narg, nres, base);
        signal(SIGINT, SIG_DFL); /* reset C-signal handler */
        lua_remove(L, base);     /* remove message handler from the stack */
        return status;
    }
    int report(lua_State* L, int status)
    {
        if (status != LUA_OK)
        {
            const char* msg = lua_tostring(L, -1);
            l_message(progname, msg);
            lua_pop(L, 1); /* remove message */
        }
        return status;
    }
    void l_message(const char* pname, const char* msg)
    {
        if (pname)
            lua_writestringerror("%s: ", pname);
        lua_writestringerror("%s\n", msg);
    }

public:
    lua_State* l;
    const char* progname = "lua";
};
TEST_F(JksBasicGrammar, valname)
{
    string validName[] = {"_var = 1", "var = 1", "Var = 1", "_2v = 1"};
    string invalidName[] = {"4v = 1", "if=1"};

    int err = 0;
    for (auto cmd : validName)
    {
        err = dostring(cmd.c_str(), cmd.c_str());
        ASSERT_EQ(err, LUA_OK) << cmd.c_str();
    }
    for (auto cmd : invalidName)
    {
        err = dostring(cmd.c_str(), cmd.c_str());
        ASSERT_EQ(err, LUA_ERRSYNTAX);
    }
}

TEST_F(JksBasicGrammar, keyword)
{
    string invalidName[] = {"break", "continue", "for", "end", "else", "elif", "while"};
    for (auto cmd : invalidName)
    {
        string cmdstr = cmd + "=1";
        EXPECT_EQ(dostring(cmdstr.c_str(), cmdstr.c_str()), LUA_ERRSYNTAX);
    }
}

TEST_F(JksBasicGrammar, comment)
{
    string cmd = "#a=11 #a=2";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    lua_getglobal(l, "a");
    EXPECT_EQ(lua_tointeger(l, -1), 0);

    string cmd2 = "a=11 #a=2";
    EXPECT_EQ(dostring(cmd2.c_str(), cmd2.c_str()), LUA_OK);
    lua_getglobal(l, "a");
    EXPECT_EQ(lua_tointeger(l, -1), 11);
}

TEST_F(JksBasicGrammar, string)
{
    string cmd = "s=\"this is a \\\"string\\\" \\' \\t \\r \\n a\"";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TSTRING, lua_getglobal(l, "s"));
    auto s = lua_tostring(l, -1);
    EXPECT_STREQ(s, "this is a \"string\" \' \t \r \n a");
}

TEST_F(JksBasicGrammar, array)
{
    string cmd = "a=[1,2,3,4,5] a0=a[0] a1=a[1] a4=a[4]";
    ASSERT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);

    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "a0"));
    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "a1"));
    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "a4"));
    EXPECT_EQ(lua_tonumber(l, -1), 5);
    EXPECT_EQ(lua_tonumber(l, -2), 2);
    EXPECT_EQ(lua_tonumber(l, -3), 1);
}

TEST_F(JksBasicGrammar, arrayNegIndex)
{
    string cmd = "a=[1,2,3,4,5] a1=a[-4] a1_=a[1]";
    ASSERT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);

    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "a1"));
    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "a1_"));
    EXPECT_EQ(lua_tonumber(l, -1), 2);
    EXPECT_EQ(lua_tonumber(l, -2), 2);
}

TEST_F(JksBasicGrammar, arrayRangeIndex)
{
    string cmd = "a=[1,2,3,4,5] a14=a[1:3] b=a14[1]";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "b"));
    EXPECT_EQ(lua_tonumber(l, -1), 3);
}

TEST_F(JksBasicGrammar, arrayRange2Index)
{
    string cmd = "a=[1,2,3,4,5] a14=a[0:4:2] b=a14[2]";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "b"));
    EXPECT_EQ(lua_tonumber(l, -1), 5);
}

TEST_F(JksBasicGrammar, arithmeticOperator)
{
    string cmd = "a=2*1+3/2-5%2*2+2**2*2";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "a"));
    ASSERT_EQ(lua_tonumber(l, -1), 9.5);
}
TEST_F(JksBasicGrammar, logicalOperator_not)
{
    string cmd = "t0=!1 t1=!0";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t0"));
    EXPECT_EQ(lua_tonumber(l, -1), 0);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t1"));
}
TEST_F(JksBasicGrammar, logicalOperator_and2)
{
    string cmd = "t0=true&&false";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t0"));
    EXPECT_EQ(lua_tonumber(l, -1), false);

    cmd = "t0=1&&0";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t0"));
    EXPECT_EQ(lua_tonumber(l, -1), false);
}
TEST_F(JksBasicGrammar, logicalOperator_and)
{
    string cmd = "t0=0&&1 t1=1&&0 t2=0&&1 t3=0&&0 t4=0&&true t5=0&&false t6=1&&true t7=1&&false";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t0"));
    EXPECT_EQ(lua_tonumber(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t1"));
    EXPECT_EQ(lua_tonumber(l, -1), false);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t2"));
    EXPECT_EQ(lua_tonumber(l, -1), false);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t3"));
    EXPECT_EQ(lua_tonumber(l, -1), false);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t4"));
    EXPECT_EQ(lua_tonumber(l, -1), false);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t5"));
    EXPECT_EQ(lua_tonumber(l, -1), false);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t6"));
    EXPECT_EQ(lua_tonumber(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t7"));
    EXPECT_EQ(lua_tonumber(l, -1), false);
}
TEST_F(JksBasicGrammar, logicalOperator_or)
{
    string cmd = "t0=0||1 t1=1||0 t2=0||1 t3=0||0 t4=0||true t5=0||false t6=1||true t7=1||false";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t0"));
    EXPECT_EQ(lua_tonumber(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t1"));
    EXPECT_EQ(lua_tonumber(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t2"));
    EXPECT_EQ(lua_tonumber(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t3"));
    EXPECT_EQ(lua_tonumber(l, -1), false);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t4"));
    EXPECT_EQ(lua_tonumber(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t5"));
    EXPECT_EQ(lua_tonumber(l, -1), false);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t6"));
    EXPECT_EQ(lua_tonumber(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t7"));
    EXPECT_EQ(lua_tonumber(l, -1), true);
}
TEST_F(JksBasicGrammar, logicalOperator_eq)
{
    string cmd = "t0=1==false t1=1==true t2=0==false t3=0==true";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t0"));
    EXPECT_EQ(lua_toboolean(l, -1), false);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t1"));
    EXPECT_EQ(lua_toboolean(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t2"));
    EXPECT_EQ(lua_toboolean(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t3"));
    EXPECT_EQ(lua_toboolean(l, -1), false);
}
TEST_F(JksBasicGrammar, logicalOperator_ne)
{
    string cmd = "t0=1!=false t1=1!=true t2=0!=false t3=0!=true";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t0"));
    EXPECT_EQ(lua_toboolean(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t1"));
    EXPECT_EQ(lua_toboolean(l, -1), false);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t2"));
    EXPECT_EQ(lua_toboolean(l, -1), false);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t3"));
    EXPECT_EQ(lua_toboolean(l, -1), true);
}
TEST_F(JksBasicGrammar, logicalOperator_greater)
{
    string cmd = "t0=1>2 t1=2>=2 t2=\"ba\">\"ab\" t3=1>=\"ab\"";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t0"));
    EXPECT_EQ(lua_toboolean(l, -1), false);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t1"));
    EXPECT_EQ(lua_toboolean(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t2"));
    EXPECT_EQ(lua_toboolean(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t3"));
    EXPECT_EQ(lua_toboolean(l, -1), false);
}
TEST_F(JksBasicGrammar, logicalOperator_less)
{
    string cmd = "t0=1<2 t1=2<=2 t2=\"ba\"<\"ab\" t3=1<=\"ab\"";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t0"));
    EXPECT_EQ(lua_toboolean(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t1"));
    EXPECT_EQ(lua_toboolean(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t2"));
    EXPECT_EQ(lua_toboolean(l, -1), false);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t3"));
    EXPECT_EQ(lua_toboolean(l, -1), false);
}
TEST_F(JksBasicGrammar, bitOperator)
{
    string cmd = "t0=1^1 t1=1^0 t2=0^1 t3=0^0";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t0"));
    EXPECT_EQ(lua_toboolean(l, -1), false);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t1"));
    EXPECT_EQ(lua_toboolean(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t2"));
    EXPECT_EQ(lua_toboolean(l, -1), true);
    EXPECT_EQ(LUA_TBOOLEAN, lua_getglobal(l, "t3"));
    EXPECT_EQ(lua_toboolean(l, -1), false);
}
TEST_F(JksBasicGrammar, ifelse)
{
    string cmd = "a=1 b=1 if(b): a=2 else: a=3 end";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "a"));
    EXPECT_EQ(lua_tonumber(l, -1), 2);
    cmd = "a=1 b=0 if(b): a=2 else: a=3 end";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "a"));
    EXPECT_EQ(lua_tonumber(l, -1), 3);
    cmd = "a=1 b=0 if(false): a=2 end";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "a"));
    EXPECT_EQ(lua_tonumber(l, -1), 1);
}
TEST_F(JksBasicGrammar, ifelifelse)
{
    string cmd = "a=1 if(false): a=2 elif(true): a=4 else: a=3 end";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "a"));
    EXPECT_EQ(lua_tonumber(l, -1), 4);
    cmd = "a=1 if(false): a=2 elif(0): a=4 else: a=3 end";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "a"));
    EXPECT_EQ(lua_tonumber(l, -1), 3);
    cmd = "a=1 if(false): a=2 elif(0): a=4 end";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "a"));
    EXPECT_EQ(lua_tonumber(l, -1), 1);
}
TEST_F(JksBasicGrammar, whileStatement)
{
    string cmd = "i=0 while(i<4): i=i+1 end";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "i"));
    EXPECT_EQ(lua_tonumber(l, -1), 4);
}
TEST_F(JksBasicGrammar, breakStatement)
{
    string cmd = "i=0 while(i<4): if(i==3): break; end i=i+1 end";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "i"));
    EXPECT_EQ(lua_tonumber(l, -1), 3);
}
TEST_F(JksBasicGrammar, functionStatement)
{
    string cmd = "i=0 a=1 while(i<4):  i=i+1 if(i%2==0): continue; end a=a+1 end";
    EXPECT_EQ(dostring(cmd.c_str(), cmd.c_str()), LUA_OK);
    EXPECT_EQ(LUA_TNUMBER, lua_getglobal(l, "a"));
    EXPECT_EQ(lua_tonumber(l, -1), 3);
}